package upm.pb4agent.team2.environment;



import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Date;

import upm.pb4agent.ontology.AssignBoat;
import upm.pb4agent.ontology.DenyRow;
import upm.pb4agent.ontology.RaceStarted;
import upm.pb4agent.ontology.RealRow;
import upm.pb4agent.ontology.Row;
import upm.pb4agent.ontology.Seat;
import upm.pb4agent.ontology.SubscribeReferee;
import upm.pb4agent.team2.Constants;
import upm.pb4agent.team2.Team2BaseAgent;
import upm.pb4agent.team2.internalOntology.PickSeatForward;
import upm.pb4agent.team2.internalOntology.TeamRacing;
import upm.pb4agent.team2.internalOntology.WatcherReference;

import jade.content.AgentAction;
import jade.content.Concept;
import jade.content.ContentElement;
import jade.content.lang.Codec.CodecException;
import jade.content.onto.OntologyException;
import jade.content.onto.basic.Action;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.SimpleBehaviour;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.util.leap.HashMap;
import jade.util.leap.Iterator;

// imports used by SubscriptionInitiator
/*
import jade.proto.SubscriptionInitiator;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPANames;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import java.util.Vector;
*/

/**
 * 
 * @version 1.0
 * @author Alejandro Bustamante
 */
public class Watcher extends Team2BaseAgent 
{
	private static final long serialVersionUID = -5445195896951732191L;
	
	private HashMap rowerStatsHash = new HashMap();

	private HashMap oarSeatsHash = new HashMap();

	private AID boatAID;
	private AID refereeAID;

	// Configurable properties (read from config.properties file)
	
	private Long row_seconds;
	private Long time_change_seat;
	
	private float front_oar_x_coef;
	private float front_oar_y_coef;
	
	private float back_oar_x_coef;
	private float back_oar_y_coef;

	private float impulse_weak;
	private float impulse_medium;
	private float impulse_strong;
	
	private float rower1_strength;
	private float rower2_strength;
	private float rower3_strength;
	private float rower4_strength;
	private float rower1_resistance;
	private float rower2_resistance;
	private float rower3_resistance;
	private float rower4_resistance;
	private float rower1_agility;
	private float rower2_agility;
	private float rower3_agility;
	private float rower4_agility;
	
	protected void setup() 
	{
		super.setup();
		
		// not needed anymore, now there's 1 watcher per team
		//registerOnDF(Constants.WATCHER);

		try 
		{
			row_seconds = Long.valueOf(getPropertyFromConfigFile("row_seconds")) * 1000;
			time_change_seat = Long.valueOf(getPropertyFromConfigFile("time_change_seat"));
			
			front_oar_x_coef = Float.valueOf(getPropertyFromConfigFile("front_oar_x_coef"));
			front_oar_y_coef = Float.valueOf(getPropertyFromConfigFile("front_oar_y_coef"));
			
			back_oar_x_coef = Float.valueOf(getPropertyFromConfigFile("back_oar_x_coef"));
			back_oar_y_coef = Float.valueOf(getPropertyFromConfigFile("back_oar_y_coef"));

			impulse_weak = Float.valueOf(getPropertyFromConfigFile("impulse_weak"));
			impulse_medium = Float.valueOf(getPropertyFromConfigFile("impulse_medium"));
			impulse_strong = Float.valueOf(getPropertyFromConfigFile("impulse_strong"));

			rower1_strength = Float.valueOf(getPropertyFromConfigFile("rower1_strength"));
			rower2_strength = Float.valueOf(getPropertyFromConfigFile("rower2_strength"));
			rower3_strength = Float.valueOf(getPropertyFromConfigFile("rower3_strength"));
			rower4_strength = Float.valueOf(getPropertyFromConfigFile("rower4_strength"));
			rower1_resistance = Float.valueOf(getPropertyFromConfigFile("rower1_resistance"));
			rower2_resistance = Float.valueOf(getPropertyFromConfigFile("rower2_resistance"));
			rower3_resistance = Float.valueOf(getPropertyFromConfigFile("rower3_resistance"));
			rower4_resistance = Float.valueOf(getPropertyFromConfigFile("rower4_resistance"));
			rower1_agility = Float.valueOf(getPropertyFromConfigFile("rower1_agility"));
			rower2_agility = Float.valueOf(getPropertyFromConfigFile("rower2_agility"));
			rower3_agility = Float.valueOf(getPropertyFromConfigFile("rower3_agility"));
			rower4_agility = Float.valueOf(getPropertyFromConfigFile("rower4_agility"));
		}
		catch (NumberFormatException e) 
		{
			e.printStackTrace();
		} 
		catch (FileNotFoundException e) 
		{
			e.printStackTrace();
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}

		// build the oarSeatsHash that we'll use to validate that rows come from valid seats
		String[] arr = {Constants.SEAT_1_ID, Constants.SEAT_2_ID};
		oarSeatsHash.put(Constants.OAR_1_ID, arr);
		
		String[] arr2 = {Constants.SEAT_2_ID, Constants.SEAT_3_ID};
		oarSeatsHash.put(Constants.OAR_2_ID, arr2);
		
		String[] arr3 = {Constants.SEAT_4_ID, Constants.SEAT_5_ID};
		oarSeatsHash.put(Constants.OAR_3_ID, arr3);
		
		String[] arr4 = {Constants.SEAT_5_ID, Constants.SEAT_6_ID};
		oarSeatsHash.put(Constants.OAR_4_ID, arr4);
		
		
		
		addBehaviour(new MessageListener(this));
		addBehaviour(new SubscribeToReferee(this));
	}

	// -------------------------------------------
	// --------------- CLASSES -------------------
	// -------------------------------------------

	class SubscribeToReferee extends SimpleBehaviour 
	{

		private static final long serialVersionUID = -3120368223362591180L;
		private Boolean subscriptionSent = false;

		public SubscribeToReferee(Agent agent) 
		{
			super(agent);
		}

		@Override
		public void action() 
		{
			refereeAID = findAgentOnDF(myAgent, Constants.REFEREE);
			
			if (refereeAID != null)
			{
				sendSubscription(refereeAID);
			}
			
			
		}
		
		private void sendSubscription(AID receiverAID)
		{
			printOut("sending subscription request to referee...");
			
			SubscribeReferee subscribeReferee = new SubscribeReferee();
			Action action = new Action(receiverAID, subscribeReferee);
			
			sendMessage(receiverAID, ACLMessage.SUBSCRIBE, action, SubscribeReferee.class.getSimpleName().toString());
			
			subscriptionSent = true;
		}

		@Override
		public boolean done() 
		{
			return subscriptionSent;
		}
	}

	class MessageListener extends SimpleBehaviour 
	{
		private static final long serialVersionUID = 5929535427381976539L;

		public MessageListener(Agent agent) 
		{
			super(agent);
		}

		@Override
		public void action() 
		{
			ACLMessage aclMessage = receive(MessageTemplate.and(MessageTemplate.MatchLanguage(codec.getName()), MessageTemplate.or(MessageTemplate.MatchOntology(ontology.getName()), MessageTemplate.MatchOntology(internalOntology.getName()))));

			printReceivedProtocol(aclMessage);
			
			if (aclMessage != null) 
			{
				RowerStats rowerStats;
				
				if (aclMessage.getPerformative() == ACLMessage.INFORM) 
				{
					try 
					{
						ContentElement contentElement = null;
						contentElement = getContentManager().extractContent(aclMessage);
						
						if (contentElement instanceof AgentAction)
						{
							Action action = (Action) contentElement;
							Concept concept = action.getAction();
							
							if (concept instanceof RaceStarted)
							{
								//The race has started
								printOut("RaceStarted received!");
							}
							
							else if (concept instanceof TeamRacing) 
							{
								//printOut("<internal ontology protocol> TeamRacing received!");
								
								TeamRacing teamRacing = ((TeamRacing) concept);
								
								String boatName = "Boat"+teamRacing.getTeamID().getValue();
								
								boatAID = createAndStartAgent(boatName, "upm.pb4agent.team2.environment.Boat", new String[] { teamRacing.getTeamID().getValue() });

								AssignBoat assignBoat = buildAssignBoat(boatAID);
								
								Iterator itr = teamRacing.getAllRowerAIDs();
								
								while(itr.hasNext()) 
								{
									AID rowerID = (AID) itr.next();
									
									sendMessage(rowerID, ACLMessage.INFORM, new Action(rowerID, assignBoat), AssignBoat.class.getSimpleName().toString());
								}
								
								WatcherReference myRef = new WatcherReference();
								myRef.setWatcherAID(myAgent.getAID());
								
								sendInternalMessage(boatAID, ACLMessage.INFORM, new Action(boatAID, myRef), WatcherReference.class.getSimpleName().toString());
							}
							
							else if (concept instanceof PickSeatForward)
							{
								// keep track of the time it will take the rower to change seat
								rowerStats = getRowerStats(((PickSeatForward)concept).getRowerAID());
								
								rowerStats.timeOfLastPickSeat = new Date().getTime();
								rowerStats.currentSeatID = ((PickSeatForward)concept).getSeatID();
								
								rowerStatsHash.put(rowerStats.rowerAID, rowerStats);
							}
							
						}
					} 
					catch (CodecException e) 
					{
						e.printStackTrace();
					} 
					catch (OntologyException oe) 
					{
						oe.printStackTrace();
					}
					
				} 
				else if (aclMessage.getPerformative() == ACLMessage.REQUEST) 
				{
					try 
					{

						ContentElement contentElement = null;
						contentElement = getContentManager().extractContent(aclMessage);
						
						if (contentElement instanceof AgentAction)
						{
							Action action = (Action) contentElement;
							Concept concept = action.getAction();
							
							if (concept instanceof Row)
							{
								//Request to row received
								
								Row row = ((Row) concept);
								
								rowerStats = getRowerStats(aclMessage.getSender());
								
								printOut("Received a Row attempt from "+rowerStats.rowerAID.getLocalName()+" on oar "+row.getOarID());
								
								if (rowerStats != null) // This must be true ALWAYS
								{
									//Verify if the row is allowable
									boolean allowRow = checkIfRowIsAllowed(rowerStats, row, new Date().getTime());
									
									if (allowRow)
									{
										//calculate the RealRow
										RealRow realRow = calculateRealRow(rowerStats, row);
										
										//Send RealRow instance to Boat
										sendMessage(row.getBoat().getID(), ACLMessage.INFORM, new Action(row.getBoat().getID(), realRow), RealRow.class.getSimpleName().toString());

										//Send RealRow instance to Rower
										sendMessage(rowerStats.rowerAID, ACLMessage.INFORM, new Action(rowerStats.rowerAID, realRow), RealRow.class.getSimpleName().toString());
										
										//printOut("Accepted row on oarID: "+row.getOarID()+" from " + rowerStats.rowerAID.getLocalName());
									}
									else
									{
										//send a deny back to the rower
										DenyRow denyRow = new DenyRow();
												denyRow.setOarID(row.getOarID());
										
										//Send DenyRow instance to Boat
										sendMessage(row.getBoat().getID(), ACLMessage.REFUSE, new Action(row.getBoat().getID(), denyRow), DenyRow.class.getSimpleName().toString());
										
										//Send DenyRow instance to Rower
										sendMessage(rowerStats.rowerAID, ACLMessage.REFUSE, new Action(rowerStats.rowerAID, denyRow), DenyRow.class.getSimpleName().toString());
									
										printOut("Denied row on oarID: "+row.getOarID()+" from " + rowerStats.rowerAID.getLocalName());
									}
									
									
								}
								else
								{
									//Debugging
									printOut("Error: !!!! corresponding RowerStats instance not found inside the rowerStatsHash !!!!");
								}
							}
						}
					}	
					catch (CodecException e) 
					{
						e.printStackTrace();
					} 
					catch (OntologyException oe) 
					{
						oe.printStackTrace();
					}
				}
				else if (aclMessage.getPerformative() == ACLMessage.AGREE) 
				{
					printOut("received AGREE for subscription to referee.");
				} 
				else if (aclMessage.getPerformative() == ACLMessage.NOT_UNDERSTOOD) 
				{
					// Message format is wrong
					printOut("received NOT_UNDERSTOOD from subscription to referee.");
				}
			} 
			else 
			{
				// No message yet
				block();
			}
		}
		
		private RowerStats getRowerStats(AID agentID)
		{
			if (!rowerStatsHash.containsKey(agentID))
			{
				RowerStats rowerStats = new RowerStats(); 
				rowerStats.rowerAID = agentID;
				rowerStats.rowerNumber = Integer.parseInt(agentID.getLocalName().substring(agentID.getLocalName().length() - 1));
				
				float strength = 0;
				float agility = 0;
				float resistance = 0;
				
				switch (rowerStats.rowerNumber)
				{
					case (1):
					{
						strength = rower1_strength;
						agility = rower1_agility;
						resistance = rower1_resistance;
						break;
					}
					case (2):
					{
						strength = rower2_strength;
						agility = rower2_agility;
						resistance = rower2_resistance;
						break;
					}
					case (3):
					{
						strength = rower3_strength;
						agility = rower3_agility;
						resistance = rower3_resistance;
						break;
					}
					case (4):
					{
						strength = rower4_strength;
						agility = rower4_agility;
						resistance = rower4_resistance;
						break;
					}
					default:
					{
						printOut("!!! rowerNumber not set on rowerStats for "+rowerStats.rowerAID.getLocalName()+" !!!");
						break;
					}
				}
				
				rowerStats.strength = strength;
				rowerStats.agility = agility;
				rowerStats.resistance = resistance;
				rowerStats.tiredness = 0;
				
				rowerStats.time_row = row_seconds - agility;
				
				rowerStatsHash.put(rowerStats.rowerAID, rowerStats);
			}
			
			return (RowerStats) rowerStatsHash.get(agentID);
		}
		
		private boolean checkIfRowIsAllowed(RowerStats rowerStats, Row row, Long currentTime)
		{
			boolean allowRow = false;
			
			String[] possibleSeatsArr = (String[])oarSeatsHash.get(row.getOarID());
			
			if(rowerStats.currentSeatID.equals(possibleSeatsArr[0]) || rowerStats.currentSeatID.equals(possibleSeatsArr[1]))
			{
				Long lastTime = rowerStats.timeOfLastPickSeat;
				if (lastTime != null)
				{
					if (currentTime - lastTime >= time_change_seat) // check that the rower is not changing seats at the moment
					{
						if (rowerStats.oarTimesHash.containsKey(row.getOarID())) // check that the rower has rowed before
						{
							lastTime = rowerStats.oarTimesHash.get(row.getOarID());
							
							if(currentTime - lastTime >= rowerStats.time_row) // check that the rower is not rowing
							{
								//rowing is within the time allowed
								allowRow = true;
							}
							else
							{
								//printOut("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 111111111111111111111111111111 denying row because "+rowerStats.rowerAID.getLocalName()+" is rowing -> currentTime - lastTime = "+(currentTime - lastTime)+" rowerStats.time_row = "+rowerStats.time_row);
							}
						}
						else // the rower has not rowed before
						{
							//add it to the hash and allow the row
							rowerStats.oarTimesHash.put(row.getOarID(), currentTime);
							allowRow = true;
						}
					}
					else
					{
						//printOut("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 222222222222222222222222222222 denying row because rower is changing seats -> currentTime - lastTime = "+(currentTime - lastTime)+" time_change_seat = "+time_change_seat);
						
					}
				}
				else
				{
					//printOut("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 3333333333333333333333333333 denying row because rower has not yet picked a seat (timeOfLastPickSeat == null)");
				}
			}
			else
			{
				//printOut("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 4444444444444444444444444444444 denying row because rower is attempting to row on an oar not accessible from his current seat");
			}
			return allowRow;
		}
		
		private RealRow calculateRealRow(RowerStats rowerStats, Row row)
		{
			RealRow realRow = new RealRow();
			
			float tiredness = rowerStats.tiredness;
			float strength = rowerStats.strength;
			float resistance = rowerStats.resistance;
			
			// get the intended impulse
			float impulse = getIntendedImpulse(row);
			
			// get the oar's x and y coefficients
			float[] arr = getCoefficients(row.getOarID());
			
			float x_coefficient = arr[0];
			float y_coefficient = arr[1];
			
			int x = (int) Math.ceil(impulse * (strength - tiredness) * x_coefficient);
			int y = (int) Math.ceil(impulse * (strength - tiredness) * y_coefficient);

			if (x < 0 || y < 0) // means the rower is exhausted
			{
				x = 0;
				y = 0;
			}
			
			if (row.getDirection() == Constants.ROW_DIRECTION_BACKWARD)
			{
				x = (x * -1);
				y = (y * -1);
			}
			
			realRow.setX(x);
			realRow.setY(y);
			realRow.setOarID(row.getOarID());
			
			updateRowerStats(rowerStats, impulse, resistance);
			
			return realRow;
		}
		
		private float getIntendedImpulse(Row row)
		{
			float impulse = 0;
			if (row.getStrength() == Constants.ROW_STRENGTH_WEAK)
			{
				impulse = impulse_weak;
			} 
			else if (row.getStrength() == Constants.ROW_STRENGTH_MEDIUM)
			{
				impulse = impulse_medium;
			} 
			else if (row.getStrength() == Constants.ROW_STRENGTH_STRONG)
			{
				impulse = impulse_strong;
			}
			
			return impulse;
		}
		
		/**
		 * Returns an array containing the x and y coefficients of the oarID passed.
		 * oar_x_coef is returned at index 0 of the array, oar_y_coef is returned at index 1 of the array.
		 * @param oarID
		 * @return
		 */
		private float[] getCoefficients(String oarID)
		{
			float[] arr = new float[2];
			
			if (oarID.equals(Constants.OAR_1_ID) || oarID.equals(Constants.OAR_2_ID))
			{
				arr[0] = front_oar_x_coef;
				arr[1] = front_oar_y_coef;
			}
			else if (oarID.equals(Constants.OAR_3_ID) || oarID.equals(Constants.OAR_4_ID))
			{
				arr[0] = back_oar_x_coef;
				arr[1] = back_oar_y_coef;
			}
			
			return arr;
		}
		
		private void updateRowerStats(RowerStats rowerStats, float impulse, float resistance)
		{
			//update tiredness
			rowerStats.tiredness = rowerStats.tiredness + impulse * (1 - resistance);
			
			//update time_row
			rowerStats.time_row = rowerStats.time_row + rowerStats.tiredness;
			
			//put back into hash
			rowerStatsHash.put(rowerStats.rowerAID, rowerStats);
		}
		
		private AssignBoat buildAssignBoat(AID boatAID)
		{

			upm.pb4agent.ontology.Boat boat = new upm.pb4agent.ontology.Boat();
			boat.setID(boatAID);
			
			Seat seat = new Seat();
			seat.setSeatID(Constants.SEAT_1_ID);
			boat.addSeats(seat);
			
			seat = new Seat();
			seat.setSeatID(Constants.SEAT_2_ID);
			boat.addSeats(seat);
			
			seat = new Seat();
			seat.setSeatID(Constants.SEAT_3_ID);
			boat.addSeats(seat);
			
			seat = new Seat();
			seat.setSeatID(Constants.SEAT_4_ID);
			boat.addSeats(seat);
			
			seat = new Seat();
			seat.setSeatID(Constants.SEAT_5_ID);
			boat.addSeats(seat);
			
			seat = new Seat();
			seat.setSeatID(Constants.SEAT_6_ID);
			boat.addSeats(seat);
			
			AssignBoat assignBoat = new AssignBoat();
			assignBoat.setBoat(boat);
			
			return assignBoat;
		}

		@Override
		public boolean done() 
		{
			return false;
		}

	}


	// ----------------------------------------------------------------------------------------------------------------------
	// -------- SUBSCRIPTION INITIATOR : USE THIS BEHAVIOUR TO RESPOND TO SUBSCRIPTIONS USING SUBSCRIPTION INITIATOR --------
	// ----------------------------------------------------------------------------------------------------------------------

	/*
	class SubscribeToReferee extends SubscriptionInitiator 
	{
		private static final long serialVersionUID = -763981736866356752L;

		public SubscribeToReferee(Agent agent) 
		{
			super(agent, new ACLMessage(ACLMessage.SUBSCRIBE)); 
		}
		
        protected Vector<ACLMessage> prepareSubscriptions(ACLMessage subscription) 
        {     
        	subscription.setProtocol(FIPANames.InteractionProtocol.FIPA_SUBSCRIBE);
        	AID refereeAID = findAgentOnDF(myAgent, Constants.REFEREE);
            subscription.addReceiver(refereeAID);   // the agent supplying a subscription service (has a responder role) 
            subscription.setContent(Constants.REFEREE_SUBSCRIPTION);   // the subscription content - from ontology?
            Vector<ACLMessage> v = new Vector<ACLMessage>(); 
            v.addElement(subscription); 
            return v; 
        } 
        
        protected void handleAgree(ACLMessage agree) 
        {
        	printOut("AGREE message received from Referee upon subscription request");
        }

        protected void handleInform(ACLMessage inform) 
        { 
            // handle inform messages from the subscription service that was subscribed to 
        	printOut("DEBUG: inform heard at Watcher.handleInform()");
        } 

        protected void handleRefuse(ACLMessage refuse) 
        { 
        	printOut("REFUSE message received from Referee upon subscription request");
        } 
		
	}
	 */
	

}
